﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Markup;

namespace BlueRoseGames.Controls.Text
{
    [ContentProperty("Inlines")]
    public partial class PathTextBlock
    {
        string text = "";
        GeometryGroup pathGroup;
        Transform transform = null;
        bool textChanged = false;
        TimeSpan updateInterval = TimeSpan.Zero;
        DateTime lastUpdateTime = DateTime.MinValue;
        List<Inline> inlines;

        public TimeSpan UpdateInterval
        {
            get
            {
                return updateInterval;
            }
            set
            {
                updateInterval = value;
            }
        }

        public Transform TextTransform
        {
            get
            {
                return transform;
            }
            set
            {
                transform = value;
            }
        }

        public Brush Fill
        {
            get
            {
                return textPath.Fill;
            }
            set
            {
                textPath.Fill = value;
            }
        }

        public double StrokeThickness
        {
            get
            {
                return textPath.StrokeThickness;
            }
            set
            {
                textPath.StrokeThickness = value;
            }
        }

        public Brush Stroke
        {
            get
            {
                return textPath.Stroke;
            }
            set
            {
                textPath.Stroke = value;
            }
        }

        public string Font
        {
            get;
            set;
        }

        public string Text
        {
            get
            {
                return text;
            }
            set
            {
                text = value;
                textChanged = true;
            }
        }

        private void RenderText()
        {
            Font f = FontManager.GetFont(Font);
            double scale = FontSize / f.PointSize; 
            double offsetX = 0;
            double offsetY = 0;
            Point curPoint = new Point();
            foreach (PathGeometry g in pathGroup.Children)
            {
                foreach (PathFigure fg in g.Figures)
                {
                    foreach (PathSegment s in fg.Segments)
                    {
                        SegmentPool.Instance.AddSegment(s);
                    }
                    fg.Segments.Clear();
                }
                g.Figures.Clear();
            }
            pathGroup.Children.Clear();
            textPath.Data = null;
            pathGroup = new GeometryGroup();
            string renderText = text;

            RenderFragment(f, scale, ref offsetX, offsetY, ref curPoint, renderText);
            foreach (Inline inline in inlines)
            {
                if (inline is Run)
                {
                    RenderFragment(f, scale, ref offsetX, offsetY, ref curPoint, (inline as Run).Text);
                }
                else if (inline is LineBreak)
                {
                    offsetX = 0;
                    offsetY += f.Baseline * f.PointSize;
                }
            }

            textPath.Data = pathGroup;
            if (transform != null) transform.Changed = false;
            textChanged = false;
        }

        private void RenderFragment(Font f, double scale, ref double offsetX, double offsetY, ref Point curPoint, string renderText)
        {
            if (renderText == null) return;
            foreach (char c in renderText)
            {
                if (f.Glyphs.ContainsKey(c) == false) continue;
                Glyph g = f.Glyphs[c];

                PathGeometry pg = g.PathGeometry;
                curPoint.X = offsetX;
                curPoint.Y = offsetY + f.Baseline * f.PointSize;
                TranslateAndScale(pg, curPoint, scale);
                Transform(pg);
                pathGroup.Children.Add(pg);
                //                Transform(p.Data as PathGeometry);
                offsetX += g.Advance.Width * f.PointSize;
            }
        }

        public Point TranslateAndScalePoint(Point p, Point offset, double scale)
        {
            return new Point((offset.X + p.X) * scale, (offset.Y + p.Y) * scale);
        }

        public void TranslateAndScale(PathGeometry pg, Point offset, double scale)
        {
            foreach (PathFigure fg in pg.Figures)
            {
                fg.StartPoint = TranslateAndScalePoint(fg.StartPoint, offset, scale);
                foreach (PathSegment s in fg.Segments)
                {
                    if (s is LineSegment)
                    {
                        LineSegment ls = s as LineSegment;
                        ls.Point = TranslateAndScalePoint(ls.Point, offset, scale);
                    }
                    else if (s is QuadraticBezierSegment)
                    {
                        QuadraticBezierSegment qs = s as QuadraticBezierSegment;
                        qs.Point1 = TranslateAndScalePoint(qs.Point1, offset, scale);
                        qs.Point2 = TranslateAndScalePoint(qs.Point2, offset, scale);
                    }
                }
            }
        }

        private void Transform(PathGeometry pg)
        {
            if (transform == null) return;
            foreach (PathFigure fg in pg.Figures)
            {
                fg.StartPoint = transform.TransformPoint(fg.StartPoint);
                foreach (PathSegment s in fg.Segments)
                {
                    if (s is LineSegment)
                    {
                        LineSegment ls = s as LineSegment;
                        ls.Point = transform.TransformPoint(ls.Point);
                    }
                    else if (s is QuadraticBezierSegment)
                    {
                        QuadraticBezierSegment qs = s as QuadraticBezierSegment;
                        qs.Point1 = transform.TransformPoint(qs.Point1);
                        qs.Point2 = transform.TransformPoint(qs.Point2);
                    }
                }
            }
        }

        public PathTextBlock()
        {
            InitializeComponent();
            textChanged = false;
            this.Loaded += new RoutedEventHandler(PathTextBlock_Loaded);
            this.LayoutUpdated += new EventHandler(PathTextBlock_LayoutUpdated);
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
            inlines = new List<Inline>();
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if ((transform != null && transform.Changed) || textChanged)
            {
                if (DateTime.Now - lastUpdateTime > updateInterval)
                {
                    RenderText();
                    lastUpdateTime = DateTime.Now;
                }
            }
        }

        void PathTextBlock_LayoutUpdated(object sender, EventArgs e)
        {
            RenderText();
        }

        void PathTextBlock_Loaded(object sender, RoutedEventArgs e)
        {
            pathGroup = new GeometryGroup();
            textPath.Data = pathGroup;
            RenderText();
        }

        public List<Inline> Inlines
        {
            get
            {
                return inlines;
            }
        }
    }
}
